/***************************************************************************/
/*                                                                         */
/*  gxvcommn.c                                                             */
/*                                                                         */
/*    TrueTypeGX/AAT common tables validation (body).                      */
/*                                                                         */
/*  Copyright 2004, 2005, 2009                                             */
/*  by suzuki toshiya, Masatake YAMATO, Red Hat K.K.,                      */
/*  David Turner, Robert Wilhelm, and Werner Lemberg.                      */
/*                                                                         */
/*  This file is part of the FreeType project, and may only be used,       */
/*  modified, and distributed under the terms of the FreeType project      */
/*  license, LICENSE.TXT.  By continuing to use, modify, or distribute     */
/*  this file you indicate that you have read the license and              */
/*  understand and accept it fully.                                        */
/*                                                                         */
/***************************************************************************/

/***************************************************************************/
/*                                                                         */
/* gxvalid is derived from both gxlayout module and otvalid module.        */
/* Development of gxlayout is supported by the Information-technology      */
/* Promotion Agency(IPA), Japan.                                           */
/*                                                                         */
/***************************************************************************/


#include "gxvcommn.h"


/*************************************************************************/
/*                                                                       */
/* The macro FT_COMPONENT is used in trace mode.  It is an implicit      */
/* parameter of the FT_TRACE() and FT_ERROR() macros, used to print/log  */
/* messages during execution.                                            */
/*                                                                       */
#undef  FT_COMPONENT
#define FT_COMPONENT trace_gxvcommon


/*************************************************************************/
/*************************************************************************/
/*****                                                               *****/
/*****                       16bit offset sorter                     *****/
/*****                                                               *****/
/*************************************************************************/
/*************************************************************************/

static int
gxv_compare_ushort_offset(FT_UShort* a,
                          FT_UShort* b)
{
    if (*a < *b)
        return -1;
    else if (*a > *b)
        return 1;
    else
        return 0;
}


FT_LOCAL_DEF(void)
gxv_set_length_by_ushort_offset(FT_UShort * offset,
                                FT_UShort * *length,
                                FT_UShort * buff,
                                FT_UInt nmemb,
                                FT_UShort limit,
                                GXV_Validator valid)
{
    FT_UInt i;


    for (i = 0; i < nmemb; i++)
        *(length[i]) = 0;

    for (i = 0; i < nmemb; i++)
        buff[i] = offset[i];
    buff[nmemb] = limit;

    ft_qsort(buff, (nmemb + 1), sizeof(FT_UShort),
             (int(*) (const void*, const void*))gxv_compare_ushort_offset);

    if (buff[nmemb] > limit)
        FT_INVALID_OFFSET;

    for (i = 0; i < nmemb; i++)
    {
        FT_UInt j;


        for (j = 0; j < nmemb; j++)
            if (buff[j] == offset[i])
                break;

        if (j == nmemb)
            FT_INVALID_OFFSET;

        *(length[i]) = (FT_UShort)(buff[j + 1] - buff[j]);

        if (0 != offset[i] && 0 == *(length[i]))
            FT_INVALID_OFFSET;
    }
}


/*************************************************************************/
/*************************************************************************/
/*****                                                               *****/
/*****                       32bit offset sorter                     *****/
/*****                                                               *****/
/*************************************************************************/
/*************************************************************************/

static int
gxv_compare_ulong_offset(FT_ULong* a,
                         FT_ULong* b)
{
    if (*a < *b)
        return -1;
    else if (*a > *b)
        return 1;
    else
        return 0;
}


FT_LOCAL_DEF(void)
gxv_set_length_by_ulong_offset(FT_ULong * offset,
                               FT_ULong * *length,
                               FT_ULong * buff,
                               FT_UInt nmemb,
                               FT_ULong limit,
                               GXV_Validator valid)
{
    FT_UInt i;


    for (i = 0; i < nmemb; i++)
        *(length[i]) = 0;

    for (i = 0; i < nmemb; i++)
        buff[i] = offset[i];
    buff[nmemb] = limit;

    ft_qsort(buff, (nmemb + 1), sizeof(FT_ULong),
             (int(*) (const void*, const void*))gxv_compare_ulong_offset);

    if (buff[nmemb] > limit)
        FT_INVALID_OFFSET;

    for (i = 0; i < nmemb; i++)
    {
        FT_UInt j;


        for (j = 0; j < nmemb; j++)
            if (buff[j] == offset[i])
                break;

        if (j == nmemb)
            FT_INVALID_OFFSET;

        *(length[i]) = buff[j + 1] - buff[j];

        if (0 != offset[i] && 0 == *(length[i]))
            FT_INVALID_OFFSET;
    }
}


/*************************************************************************/
/*************************************************************************/
/*****                                                               *****/
/*****               scan value array and get min & max              *****/
/*****                                                               *****/
/*************************************************************************/
/*************************************************************************/


FT_LOCAL_DEF(void)
gxv_array_getlimits_byte(FT_Bytes table,
                         FT_Bytes limit,
                         FT_Byte * min,
                         FT_Byte * max,
                         GXV_Validator valid)
{
    FT_Bytes p = table;


    *min = 0xFF;
    *max = 0x00;

    while (p < limit)
    {
        FT_Byte val;


        GXV_LIMIT_CHECK(1);
        val = FT_NEXT_BYTE(p);

        *min = (FT_Byte)FT_MIN(*min, val);
        *max = (FT_Byte)FT_MAX(*max, val);
    }

    valid->subtable_length = p - table;
}


FT_LOCAL_DEF(void)
gxv_array_getlimits_ushort(FT_Bytes table,
                           FT_Bytes limit,
                           FT_UShort * min,
                           FT_UShort * max,
                           GXV_Validator valid)
{
    FT_Bytes p = table;


    *min = 0xFFFFU;
    *max = 0x0000;

    while (p < limit)
    {
        FT_UShort val;


        GXV_LIMIT_CHECK(2);
        val = FT_NEXT_USHORT(p);

        *min = (FT_Byte)FT_MIN(*min, val);
        *max = (FT_Byte)FT_MAX(*max, val);
    }

    valid->subtable_length = p - table;
}


/*************************************************************************/
/*************************************************************************/
/*****                                                               *****/
/*****                       BINSEARCHHEADER                         *****/
/*****                                                               *****/
/*************************************************************************/
/*************************************************************************/

typedef struct  GXV_BinSrchHeader_
{
    FT_UShort unitSize;
    FT_UShort nUnits;
    FT_UShort searchRange;
    FT_UShort entrySelector;
    FT_UShort rangeShift;

} GXV_BinSrchHeader;


static void
gxv_BinSrchHeader_check_consistency(GXV_BinSrchHeader* binSrchHeader,
                                    GXV_Validator valid)
{
    FT_UShort searchRange;
    FT_UShort entrySelector;
    FT_UShort rangeShift;


    if (binSrchHeader->unitSize == 0)
        FT_INVALID_DATA;

    if (binSrchHeader->nUnits == 0)
    {
        if (binSrchHeader->searchRange == 0 &&
            binSrchHeader->entrySelector == 0 &&
            binSrchHeader->rangeShift == 0)
            return;
        else
            FT_INVALID_DATA;
    }

    for (searchRange = 1, entrySelector = 1;
         (searchRange * 2) <= binSrchHeader->nUnits &&
         searchRange < 0x8000U;
         searchRange *= 2, entrySelector++)
        ;

    entrySelector--;
    searchRange = (FT_UShort)(searchRange * binSrchHeader->unitSize);
    rangeShift = (FT_UShort)(binSrchHeader->nUnits * binSrchHeader->unitSize
                             - searchRange);

    if (searchRange != binSrchHeader->searchRange ||
        entrySelector != binSrchHeader->entrySelector ||
        rangeShift != binSrchHeader->rangeShift)
    {
        GXV_TRACE(("Inconsistency found in BinSrchHeader\n"));
        GXV_TRACE(("originally: unitSize=%d, nUnits=%d, "
                   "searchRange=%d, entrySelector=%d, "
                   "rangeShift=%d\n",
                   binSrchHeader->unitSize, binSrchHeader->nUnits,
                   binSrchHeader->searchRange, binSrchHeader->entrySelector,
                   binSrchHeader->rangeShift));
        GXV_TRACE(("calculated: unitSize=%d, nUnits=%d, "
                   "searchRange=%d, entrySelector=%d, "
                   "rangeShift=%d\n",
                   binSrchHeader->unitSize, binSrchHeader->nUnits,
                   searchRange, entrySelector, rangeShift));

        if (valid->root->level >= FT_VALIDATE_PARANOID)
            FT_INVALID_DATA;
    }
}


/*
 * parser & validator of BinSrchHeader
 * which is used in LookupTable format 2, 4, 6.
 *
 * Essential parameters (unitSize, nUnits) are returned by
 * given pointer, others (searchRange, entrySelector, rangeShift)
 * can be calculated by essential parameters, so they are just
 * validated and discarded.
 *
 * However, wrong values in searchRange, entrySelector, rangeShift
 * won't cause fatal errors, because these parameters might be
 * only used in old m68k font driver in MacOS.
 *   -- suzuki toshiya <mpsuzuki@hiroshima-u.ac.jp>
 */

FT_LOCAL_DEF(void)
gxv_BinSrchHeader_validate(FT_Bytes table,
                           FT_Bytes limit,
                           FT_UShort * unitSize_p,
                           FT_UShort * nUnits_p,
                           GXV_Validator valid)
{
    FT_Bytes p = table;
    GXV_BinSrchHeader binSrchHeader;


    GXV_NAME_ENTER("BinSrchHeader validate");

    if (*unitSize_p == 0)
    {
        GXV_LIMIT_CHECK(2);
        binSrchHeader.unitSize = FT_NEXT_USHORT(p);
    }
    else
        binSrchHeader.unitSize = *unitSize_p;

    if (*nUnits_p == 0)
    {
        GXV_LIMIT_CHECK(2);
        binSrchHeader.nUnits = FT_NEXT_USHORT(p);
    }
    else
        binSrchHeader.nUnits = *nUnits_p;

    GXV_LIMIT_CHECK(2 + 2 + 2);
    binSrchHeader.searchRange = FT_NEXT_USHORT(p);
    binSrchHeader.entrySelector = FT_NEXT_USHORT(p);
    binSrchHeader.rangeShift = FT_NEXT_USHORT(p);
    GXV_TRACE(("nUnits %d\n", binSrchHeader.nUnits));

    gxv_BinSrchHeader_check_consistency(&binSrchHeader, valid);

    if (*unitSize_p == 0)
        *unitSize_p = binSrchHeader.unitSize;

    if (*nUnits_p == 0)
        *nUnits_p = binSrchHeader.nUnits;

    valid->subtable_length = p - table;
    GXV_EXIT;
}


/*************************************************************************/
/*************************************************************************/
/*****                                                               *****/
/*****                         LOOKUP TABLE                          *****/
/*****                                                               *****/
/*************************************************************************/
/*************************************************************************/

#define GXV_LOOKUP_VALUE_LOAD(P, SIGNSPEC) \
    (P += 2, gxv_lookup_value_load(P - 2, SIGNSPEC))

static GXV_LookupValueDesc
gxv_lookup_value_load(FT_Bytes p,
                      int signspec)
{
    GXV_LookupValueDesc v;


    if (signspec == GXV_LOOKUPVALUE_UNSIGNED)
        v.u = FT_NEXT_USHORT(p);
    else
        v.s = FT_NEXT_SHORT(p);

    return v;
}


#define GXV_UNITSIZE_VALIDATE(FORMAT, UNITSIZE, NUNITS, CORRECTSIZE) \
    FT_BEGIN_STMNT \
    if (UNITSIZE != CORRECTSIZE) \
    { \
        FT_ERROR(("unitSize=%d differs from" \
                  " expected unitSize=%d" \
                  " in LookupTable %s\n", \
                  UNITSIZE, CORRECTSIZE, FORMAT)); \
        if (UNITSIZE != 0 && NUNITS != 0) \
        { \
            FT_ERROR((" cannot validate anymore\n")); \
            FT_INVALID_FORMAT; \
        } \
        else \
            FT_ERROR((" forcibly continues\n")); \
    } \
    FT_END_STMNT


/* ================= Simple Array Format 0 Lookup Table ================ */
static void
gxv_LookupTable_fmt0_validate(FT_Bytes table,
                              FT_Bytes limit,
                              GXV_Validator valid)
{
    FT_Bytes p = table;
    FT_UShort i;

    GXV_LookupValueDesc value;


    GXV_NAME_ENTER("LookupTable format 0");

    GXV_LIMIT_CHECK(2 * valid->face->num_glyphs);

    for (i = 0; i < valid->face->num_glyphs; i++)
    {
        GXV_LIMIT_CHECK(2);
        if (p + 2 >= limit)     /* some fonts have too-short fmt0 array */
        {
            GXV_TRACE(("too short, glyphs %d - %d are missing\n",
                       i, valid->face->num_glyphs));
            if (valid->root->level >= FT_VALIDATE_PARANOID)
                FT_INVALID_GLYPH_ID;
            break;
        }

        value = GXV_LOOKUP_VALUE_LOAD(p, valid->lookupval_sign);
        valid->lookupval_func(i, &value, valid);
    }

    valid->subtable_length = p - table;
    GXV_EXIT;
}


/* ================= Segment Single Format 2 Loolup Table ============== */
/*
 * Apple spec says:
 *
 *   To guarantee that a binary search terminates, you must include one or
 *   more special `end of search table' values at the end of the data to
 *   be searched.  The number of termination values that need to be
 *   included is table-specific.  The value that indicates binary search
 *   termination is 0xFFFF.
 *
 * The problem is that nUnits does not include this end-marker.  It's
 * quite difficult to discriminate whether the following 0xFFFF comes from
 * the end-marker or some next data.
 *
 *   -- suzuki toshiya <mpsuzuki@hiroshima-u.ac.jp>
 */
static void
gxv_LookupTable_fmt2_skip_endmarkers(FT_Bytes table,
                                     FT_UShort unitSize,
                                     GXV_Validator valid)
{
    FT_Bytes p = table;


    while ((p + 4) < valid->root->limit)
    {
        if (p[0] != 0xFF || p[1] != 0xFF || /* lastGlyph */
            p[2] != 0xFF || p[3] != 0xFF)  /* firstGlyph */
            break;
        p += unitSize;
    }

    valid->subtable_length = p - table;
}


static void
gxv_LookupTable_fmt2_validate(FT_Bytes table,
                              FT_Bytes limit,
                              GXV_Validator valid)
{
    FT_Bytes p = table;
    FT_UShort gid;

    FT_UShort unitSize;
    FT_UShort nUnits;
    FT_UShort unit;
    FT_UShort lastGlyph;
    FT_UShort firstGlyph;
    GXV_LookupValueDesc value;


    GXV_NAME_ENTER("LookupTable format 2");

    unitSize = nUnits = 0;
    gxv_BinSrchHeader_validate(p, limit, &unitSize, &nUnits, valid);
    p += valid->subtable_length;

    GXV_UNITSIZE_VALIDATE("format2", unitSize, nUnits, 6);

    for (unit = 0, gid = 0; unit < nUnits; unit++)
    {
        GXV_LIMIT_CHECK(2 + 2 + 2);
        lastGlyph = FT_NEXT_USHORT(p);
        firstGlyph = FT_NEXT_USHORT(p);
        value = GXV_LOOKUP_VALUE_LOAD(p, valid->lookupval_sign);

        gxv_glyphid_validate(firstGlyph, valid);
        gxv_glyphid_validate(lastGlyph, valid);

        if (lastGlyph < gid)
        {
            GXV_TRACE(("reverse ordered segment specification:"
                       " lastGlyph[%d]=%d < lastGlyph[%d]=%d\n",
                       unit, lastGlyph, unit - 1, gid));
            if (valid->root->level >= FT_VALIDATE_PARANOID)
                FT_INVALID_GLYPH_ID;
        }

        if (lastGlyph < firstGlyph)
        {
            GXV_TRACE(("reverse ordered range specification at unit %d:",
                       " lastGlyph %d < firstGlyph %d ",
                       unit, lastGlyph, firstGlyph));
            if (valid->root->level >= FT_VALIDATE_PARANOID)
                FT_INVALID_GLYPH_ID;

            if (valid->root->level == FT_VALIDATE_TIGHT)
                continue; /* ftxvalidator silently skips such an entry */

            FT_TRACE4(("continuing with exchanged values\n"));
            gid = firstGlyph;
            firstGlyph = lastGlyph;
            lastGlyph = gid;
        }

        for (gid = firstGlyph; gid <= lastGlyph; gid++)
            valid->lookupval_func(gid, &value, valid);
    }

    gxv_LookupTable_fmt2_skip_endmarkers(p, unitSize, valid);
    p += valid->subtable_length;

    valid->subtable_length = p - table;
    GXV_EXIT;
}


/* ================= Segment Array Format 4 Lookup Table =============== */
static void
gxv_LookupTable_fmt4_validate(FT_Bytes table,
                              FT_Bytes limit,
                              GXV_Validator valid)
{
    FT_Bytes p = table;
    FT_UShort unit;
    FT_UShort gid;

    FT_UShort unitSize;
    FT_UShort nUnits;
    FT_UShort lastGlyph;
    FT_UShort firstGlyph;
    GXV_LookupValueDesc base_value;
    GXV_LookupValueDesc value;


    GXV_NAME_ENTER("LookupTable format 4");

    unitSize = nUnits = 0;
    gxv_BinSrchHeader_validate(p, limit, &unitSize, &nUnits, valid);
    p += valid->subtable_length;

    GXV_UNITSIZE_VALIDATE("format4", unitSize, nUnits, 6);

    for (unit = 0, gid = 0; unit < nUnits; unit++)
    {
        GXV_LIMIT_CHECK(2 + 2);
        lastGlyph = FT_NEXT_USHORT(p);
        firstGlyph = FT_NEXT_USHORT(p);

        gxv_glyphid_validate(firstGlyph, valid);
        gxv_glyphid_validate(lastGlyph, valid);

        if (lastGlyph < gid)
        {
            GXV_TRACE(("reverse ordered segment specification:"
                       " lastGlyph[%d]=%d < lastGlyph[%d]=%d\n",
                       unit, lastGlyph, unit - 1, gid));
            if (valid->root->level >= FT_VALIDATE_PARANOID)
                FT_INVALID_GLYPH_ID;
        }

        if (lastGlyph < firstGlyph)
        {
            GXV_TRACE(("reverse ordered range specification at unit %d:",
                       " lastGlyph %d < firstGlyph %d ",
                       unit, lastGlyph, firstGlyph));
            if (valid->root->level >= FT_VALIDATE_PARANOID)
                FT_INVALID_GLYPH_ID;

            if (valid->root->level == FT_VALIDATE_TIGHT)
                continue; /* ftxvalidator silently skips such an entry */

            FT_TRACE4(("continuing with exchanged values\n"));
            gid = firstGlyph;
            firstGlyph = lastGlyph;
            lastGlyph = gid;
        }

        GXV_LIMIT_CHECK(2);
        base_value = GXV_LOOKUP_VALUE_LOAD(p, GXV_LOOKUPVALUE_UNSIGNED);

        for (gid = firstGlyph; gid <= lastGlyph; gid++)
        {
            value = valid->lookupfmt4_trans((FT_UShort)(gid - firstGlyph),
                                            &base_value,
                                            limit,
                                            valid);

            valid->lookupval_func(gid, &value, valid);
        }
    }

    gxv_LookupTable_fmt2_skip_endmarkers(p, unitSize, valid);
    p += valid->subtable_length;

    valid->subtable_length = p - table;
    GXV_EXIT;
}


/* ================= Segment Table Format 6 Lookup Table =============== */
static void
gxv_LookupTable_fmt6_skip_endmarkers(FT_Bytes table,
                                     FT_UShort unitSize,
                                     GXV_Validator valid)
{
    FT_Bytes p = table;


    while (p < valid->root->limit)
    {
        if (p[0] != 0xFF || p[1] != 0xFF)
            break;
        p += unitSize;
    }

    valid->subtable_length = p - table;
}


static void
gxv_LookupTable_fmt6_validate(FT_Bytes table,
                              FT_Bytes limit,
                              GXV_Validator valid)
{
    FT_Bytes p = table;
    FT_UShort unit;
    FT_UShort prev_glyph;

    FT_UShort unitSize;
    FT_UShort nUnits;
    FT_UShort glyph;
    GXV_LookupValueDesc value;


    GXV_NAME_ENTER("LookupTable format 6");

    unitSize = nUnits = 0;
    gxv_BinSrchHeader_validate(p, limit, &unitSize, &nUnits, valid);
    p += valid->subtable_length;

    GXV_UNITSIZE_VALIDATE("format6", unitSize, nUnits, 4);

    for (unit = 0, prev_glyph = 0; unit < nUnits; unit++)
    {
        GXV_LIMIT_CHECK(2 + 2);
        glyph = FT_NEXT_USHORT(p);
        value = GXV_LOOKUP_VALUE_LOAD(p, valid->lookupval_sign);

        if (gxv_glyphid_validate(glyph, valid))
            GXV_TRACE((" endmarker found within defined range"
                       " (entry %d < nUnits=%d)\n",
                       unit, nUnits));

        if (prev_glyph > glyph)
        {
            GXV_TRACE(("current gid 0x%04x < previous gid 0x%04x\n",
                       glyph, prev_glyph));
            if (valid->root->level >= FT_VALIDATE_PARANOID)
                FT_INVALID_GLYPH_ID;
        }
        prev_glyph = glyph;

        valid->lookupval_func(glyph, &value, valid);
    }

    gxv_LookupTable_fmt6_skip_endmarkers(p, unitSize, valid);
    p += valid->subtable_length;

    valid->subtable_length = p - table;
    GXV_EXIT;
}


/* ================= Trimmed Array Format 8 Lookup Table =============== */
static void
gxv_LookupTable_fmt8_validate(FT_Bytes table,
                              FT_Bytes limit,
                              GXV_Validator valid)
{
    FT_Bytes p = table;
    FT_UShort i;

    GXV_LookupValueDesc value;
    FT_UShort firstGlyph;
    FT_UShort glyphCount;


    GXV_NAME_ENTER("LookupTable format 8");

    /* firstGlyph + glyphCount */
    GXV_LIMIT_CHECK(2 + 2);
    firstGlyph = FT_NEXT_USHORT(p);
    glyphCount = FT_NEXT_USHORT(p);

    gxv_glyphid_validate(firstGlyph, valid);
    gxv_glyphid_validate((FT_UShort)(firstGlyph + glyphCount), valid);

    /* valueArray */
    for (i = 0; i < glyphCount; i++)
    {
        GXV_LIMIT_CHECK(2);
        value = GXV_LOOKUP_VALUE_LOAD(p, valid->lookupval_sign);
        valid->lookupval_func((FT_UShort)(firstGlyph + i), &value, valid);
    }

    valid->subtable_length = p - table;
    GXV_EXIT;
}


FT_LOCAL_DEF(void)
gxv_LookupTable_validate(FT_Bytes table,
                         FT_Bytes limit,
                         GXV_Validator valid)
{
    FT_Bytes p = table;
    FT_UShort format;

    GXV_Validate_Func fmt_funcs_table[] =
    {
        gxv_LookupTable_fmt0_validate, /* 0 */
        NULL,                        /* 1 */
        gxv_LookupTable_fmt2_validate, /* 2 */
        NULL,                        /* 3 */
        gxv_LookupTable_fmt4_validate, /* 4 */
        NULL,                        /* 5 */
        gxv_LookupTable_fmt6_validate, /* 6 */
        NULL,                        /* 7 */
        gxv_LookupTable_fmt8_validate, /* 8 */
    };

    GXV_Validate_Func func;


    GXV_NAME_ENTER("LookupTable");

    /* lookuptbl_head may be used in fmt4 transit function. */
    valid->lookuptbl_head = table;

    /* format */
    GXV_LIMIT_CHECK(2);
    format = FT_NEXT_USHORT(p);
    GXV_TRACE((" (format %d)\n", format));

    if (format > 8)
        FT_INVALID_FORMAT;

    func = fmt_funcs_table[format];
    if (func == NULL)
        FT_INVALID_FORMAT;

    func(p, limit, valid);
    p += valid->subtable_length;

    valid->subtable_length = p - table;

    GXV_EXIT;
}


/*************************************************************************/
/*************************************************************************/
/*****                                                               *****/
/*****                          Glyph ID                             *****/
/*****                                                               *****/
/*************************************************************************/
/*************************************************************************/

FT_LOCAL_DEF(FT_Int)
gxv_glyphid_validate(FT_UShort gid,
                     GXV_Validator valid)
{
    FT_Face face;


    if (gid == 0xFFFFU)
    {
        GXV_EXIT;
        return 1;
    }

    face = valid->face;
    if (face->num_glyphs < gid)
    {
        GXV_TRACE((" gxv_glyphid_check() gid overflow: num_glyphs %d < %d\n",
                   face->num_glyphs, gid));
        if (valid->root->level >= FT_VALIDATE_PARANOID)
            FT_INVALID_GLYPH_ID;
    }

    return 0;
}


/*************************************************************************/
/*************************************************************************/
/*****                                                               *****/
/*****                        CONTROL POINT                          *****/
/*****                                                               *****/
/*************************************************************************/
/*************************************************************************/

FT_LOCAL_DEF(void)
gxv_ctlPoint_validate(FT_UShort gid,
                      FT_Short ctl_point,
                      GXV_Validator valid)
{
    FT_Face face;
    FT_Error error;

    FT_GlyphSlot glyph;
    FT_Outline outline;
    short n_points;


    face = valid->face;

    error = FT_Load_Glyph(face,
                          gid,
                          FT_LOAD_NO_BITMAP | FT_LOAD_IGNORE_TRANSFORM);
    if (error)
        FT_INVALID_GLYPH_ID;

    glyph = face->glyph;
    outline = glyph->outline;
    n_points = outline.n_points;


    if (!(ctl_point < n_points))
        FT_INVALID_DATA;
}


/*************************************************************************/
/*************************************************************************/
/*****                                                               *****/
/*****                          SFNT NAME                            *****/
/*****                                                               *****/
/*************************************************************************/
/*************************************************************************/

FT_LOCAL_DEF(void)
gxv_sfntName_validate(FT_UShort name_index,
                      FT_UShort min_index,
                      FT_UShort max_index,
                      GXV_Validator valid)
{
    FT_SfntName name;
    FT_UInt i;
    FT_UInt nnames;


    GXV_NAME_ENTER("sfntName");

    if (name_index < min_index || max_index < name_index)
        FT_INVALID_FORMAT;

    nnames = FT_Get_Sfnt_Name_Count(valid->face);
    for (i = 0; i < nnames; i++)
    {
        if (FT_Get_Sfnt_Name(valid->face, i, &name) != FT_Err_Ok)
            continue;

        if (name.name_id == name_index)
            goto Out;
    }

    GXV_TRACE(("  nameIndex = %d (UNTITLED)\n", name_index));
    FT_INVALID_DATA;
    goto Exit;  /* make compiler happy */

Out:
    FT_TRACE1(("  nameIndex = %d (", name_index));
    GXV_TRACE_HEXDUMP_SFNTNAME(name);
    FT_TRACE1((")\n"));

Exit:
    GXV_EXIT;
}


/*************************************************************************/
/*************************************************************************/
/*****                                                               *****/
/*****                          STATE TABLE                          *****/
/*****                                                               *****/
/*************************************************************************/
/*************************************************************************/

/* -------------------------- Class Table --------------------------- */

/*
 * highestClass specifies how many classes are defined in this
 * Class Subtable.  Apple spec does not mention whether undefined
 * holes in the class (e.g.: 0-3 are predefined, 4 is unused, 5 is used)
 * are permitted.  At present, holes in a defined class are not checked.
 *   -- suzuki toshiya <mpsuzuki@hiroshima-u.ac.jp>
 */

static void
gxv_ClassTable_validate(FT_Bytes table,
                        FT_UShort* length_p,
                        FT_UShort stateSize,
                        FT_Byte* maxClassID_p,
                        GXV_Validator valid)
{
    FT_Bytes p = table;
    FT_Bytes limit = table + *length_p;
    FT_UShort firstGlyph;
    FT_UShort nGlyphs;


    GXV_NAME_ENTER("ClassTable");

    *maxClassID_p = 3;  /* Classes 0, 2, and 3 are predefined */

    GXV_LIMIT_CHECK(2 + 2);
    firstGlyph = FT_NEXT_USHORT(p);
    nGlyphs = FT_NEXT_USHORT(p);

    GXV_TRACE((" (firstGlyph = %d, nGlyphs = %d)\n", firstGlyph, nGlyphs));

    if (!nGlyphs)
        goto Out;

    gxv_glyphid_validate((FT_UShort)(firstGlyph + nGlyphs), valid);

    {
        FT_Byte nGlyphInClass[256];
        FT_Byte classID;
        FT_UShort i;


        ft_memset(nGlyphInClass, 0, 256);


        for (i = 0; i < nGlyphs; i++)
        {
            GXV_LIMIT_CHECK(1);
            classID = FT_NEXT_BYTE(p);
            switch (classID)
            {
                /* following classes should not appear in class array */
                case 0:     /* end of text */
                case 2:     /* out of bounds */
                case 3:     /* end of line */
                    FT_INVALID_DATA;
                    break;

                case 1:     /* out of bounds */
                default:    /* user-defined: 4 - ( stateSize - 1 ) */
                    if (classID >= stateSize)
                        FT_INVALID_DATA; /* assign glyph to undefined state */

                    nGlyphInClass[classID]++;
                    break;
            }
        }
        *length_p = (FT_UShort)(p - table);

        /* scan max ClassID in use */
        for (i = 0; i < stateSize; i++)
            if ((3 < i) && (nGlyphInClass[i] > 0))
                *maxClassID_p = (FT_Byte)i; /* XXX: Check Range? */
    }

Out:
    GXV_TRACE(("Declared stateSize=0x%02x, Used maxClassID=0x%02x\n",
               stateSize, *maxClassID_p));
    GXV_EXIT;
}


/* --------------------------- State Array ----------------------------- */

static void
gxv_StateArray_validate(FT_Bytes table,
                        FT_UShort* length_p,
                        FT_Byte maxClassID,
                        FT_UShort stateSize,
                        FT_Byte* maxState_p,
                        FT_Byte* maxEntry_p,
                        GXV_Validator valid)
{
    FT_Bytes p = table;
    FT_Bytes limit = table + *length_p;
    FT_Byte clazz;
    FT_Byte entry;

    FT_UNUSED(stateSize);   /* for the non-debugging case */


    GXV_NAME_ENTER("StateArray");

    GXV_TRACE(("parse %d bytes by stateSize=%d maxClassID=%d\n",
               (int)(*length_p), stateSize, (int)(maxClassID)));

    /*
     * 2 states are predefined and must be described in StateArray:
     * state 0 (start of text), 1 (start of line)
     */
    GXV_LIMIT_CHECK((1 + maxClassID) * 2);

    *maxState_p = 0;
    *maxEntry_p = 0;

    /* read if enough to read another state */
    while (p + (1 + maxClassID) <= limit)
    {
        (*maxState_p)++;
        for (clazz = 0; clazz <= maxClassID; clazz++)
        {
            entry = FT_NEXT_BYTE(p);
            *maxEntry_p = (FT_Byte)FT_MAX(*maxEntry_p, entry);
        }
    }
    GXV_TRACE(("parsed: maxState=%d, maxEntry=%d\n",
               *maxState_p, *maxEntry_p));

    *length_p = (FT_UShort)(p - table);

    GXV_EXIT;
}


/* --------------------------- Entry Table ----------------------------- */

static void
gxv_EntryTable_validate(FT_Bytes table,
                        FT_UShort* length_p,
                        FT_Byte maxEntry,
                        FT_UShort stateArray,
                        FT_UShort stateArray_length,
                        FT_Byte maxClassID,
                        FT_Bytes statetable_table,
                        FT_Bytes statetable_limit,
                        GXV_Validator valid)
{
    FT_Bytes p = table;
    FT_Bytes limit = table + *length_p;
    FT_Byte entry;
    FT_Byte state;
    FT_Int entrySize = 2 + 2 + GXV_GLYPHOFFSET_SIZE(statetable);

    GXV_XStateTable_GlyphOffsetDesc glyphOffset;


    GXV_NAME_ENTER("EntryTable");

    GXV_TRACE(("maxEntry=%d entrySize=%d\n", maxEntry, entrySize));

    if ((maxEntry + 1) * entrySize > *length_p)
    {
        if (valid->root->level >= FT_VALIDATE_PARANOID)
            FT_INVALID_TOO_SHORT;

        /* ftxvalidator and FontValidator both warn and continue */
        maxEntry = (FT_Byte)(*length_p / entrySize - 1);
        GXV_TRACE(("too large maxEntry, shrinking to %d fit EntryTable length\n",
                   maxEntry));
    }

    for (entry = 0; entry <= maxEntry; entry++)
    {
        FT_UShort newState;
        FT_UShort flags;


        GXV_LIMIT_CHECK(2 + 2);
        newState = FT_NEXT_USHORT(p);
        flags = FT_NEXT_USHORT(p);


        if (newState < stateArray ||
            stateArray + stateArray_length < newState)
        {
            GXV_TRACE((" newState offset 0x%04x is out of stateArray\n",
                       newState));
            if (valid->root->level >= FT_VALIDATE_PARANOID)
                FT_INVALID_OFFSET;
            continue;
        }

        if (0 != ((newState - stateArray) % (1 + maxClassID)))
        {
            GXV_TRACE((" newState offset 0x%04x is not aligned to %d-classes\n",
                       newState, 1 + maxClassID));
            if (valid->root->level >= FT_VALIDATE_PARANOID)
                FT_INVALID_OFFSET;
            continue;
        }

        state = (FT_Byte)((newState - stateArray) / (1 + maxClassID));

        switch (GXV_GLYPHOFFSET_FMT(statetable))
        {
            case GXV_GLYPHOFFSET_NONE:
                glyphOffset.uc = 0; /* make compiler happy */
                break;

            case GXV_GLYPHOFFSET_UCHAR:
                glyphOffset.uc = FT_NEXT_BYTE(p);
                break;

            case GXV_GLYPHOFFSET_CHAR:
                glyphOffset.c = FT_NEXT_CHAR(p);
                break;

            case GXV_GLYPHOFFSET_USHORT:
                glyphOffset.u = FT_NEXT_USHORT(p);
                break;

            case GXV_GLYPHOFFSET_SHORT:
                glyphOffset.s = FT_NEXT_SHORT(p);
                break;

            case GXV_GLYPHOFFSET_ULONG:
                glyphOffset.ul = FT_NEXT_ULONG(p);
                break;

            case GXV_GLYPHOFFSET_LONG:
                glyphOffset.l = FT_NEXT_LONG(p);
                break;

            default:
                if (valid->root->level >= FT_VALIDATE_PARANOID)
                    FT_INVALID_FORMAT;
                goto Exit;
        }

        if (NULL != valid->statetable.entry_validate_func)
            valid->statetable.entry_validate_func(state,
                                                  flags,
                                                  &glyphOffset,
                                                  statetable_table,
                                                  statetable_limit,
                                                  valid);
    }

Exit:
    *length_p = (FT_UShort)(p - table);

    GXV_EXIT;
}


/* =========================== State Table ============================= */

FT_LOCAL_DEF(void)
gxv_StateTable_subtable_setup(FT_UShort table_size,
                              FT_UShort classTable,
                              FT_UShort stateArray,
                              FT_UShort entryTable,
                              FT_UShort * classTable_length_p,
                              FT_UShort * stateArray_length_p,
                              FT_UShort * entryTable_length_p,
                              GXV_Validator valid)
{
    FT_UShort o[3];
    FT_UShort* l[3];
    FT_UShort buff[4];


    o[0] = classTable;
    o[1] = stateArray;
    o[2] = entryTable;
    l[0] = classTable_length_p;
    l[1] = stateArray_length_p;
    l[2] = entryTable_length_p;

    gxv_set_length_by_ushort_offset(o, l, buff, 3, table_size, valid);
}


FT_LOCAL_DEF(void)
gxv_StateTable_validate(FT_Bytes table,
                        FT_Bytes limit,
                        GXV_Validator valid)
{
    FT_UShort stateSize;
    FT_UShort classTable;       /* offset to Class(Sub)Table */
    FT_UShort stateArray;       /* offset to StateArray */
    FT_UShort entryTable;       /* offset to EntryTable */

    FT_UShort classTable_length;
    FT_UShort stateArray_length;
    FT_UShort entryTable_length;
    FT_Byte maxClassID;
    FT_Byte maxState;
    FT_Byte maxEntry;

    GXV_StateTable_Subtable_Setup_Func setup_func;

    FT_Bytes p = table;


    GXV_NAME_ENTER("StateTable");

    GXV_TRACE(("StateTable header\n"));

    GXV_LIMIT_CHECK(2 + 2 + 2 + 2);
    stateSize = FT_NEXT_USHORT(p);
    classTable = FT_NEXT_USHORT(p);
    stateArray = FT_NEXT_USHORT(p);
    entryTable = FT_NEXT_USHORT(p);

    GXV_TRACE(("stateSize=0x%04x\n", stateSize));
    GXV_TRACE(("offset to classTable=0x%04x\n", classTable));
    GXV_TRACE(("offset to stateArray=0x%04x\n", stateArray));
    GXV_TRACE(("offset to entryTable=0x%04x\n", entryTable));

    if (stateSize > 0xFF)
        FT_INVALID_DATA;

    if (valid->statetable.optdata_load_func != NULL)
        valid->statetable.optdata_load_func(p, limit, valid);

    if (valid->statetable.subtable_setup_func != NULL)
        setup_func = valid->statetable.subtable_setup_func;
    else
        setup_func = gxv_StateTable_subtable_setup;

    setup_func((FT_UShort)(limit - table),
               classTable,
               stateArray,
               entryTable,
               &classTable_length,
               &stateArray_length,
               &entryTable_length,
               valid);

    GXV_TRACE(("StateTable Subtables\n"));

    if (classTable != 0)
        gxv_ClassTable_validate(table + classTable,
                                &classTable_length,
                                stateSize,
                                &maxClassID,
                                valid);
    else
        maxClassID = (FT_Byte)(stateSize - 1);

    if (stateArray != 0)
        gxv_StateArray_validate(table + stateArray,
                                &stateArray_length,
                                maxClassID,
                                stateSize,
                                &maxState,
                                &maxEntry,
                                valid);
    else
    {
        maxState = 1;   /* 0:start of text, 1:start of line are predefined */
        maxEntry = 0;
    }

    if (maxEntry > 0 && entryTable == 0)
        FT_INVALID_OFFSET;

    if (entryTable != 0)
        gxv_EntryTable_validate(table + entryTable,
                                &entryTable_length,
                                maxEntry,
                                stateArray,
                                stateArray_length,
                                maxClassID,
                                table,
                                limit,
                                valid);

    GXV_EXIT;
}


/* ================= eXtended State Table (for morx) =================== */

FT_LOCAL_DEF(void)
gxv_XStateTable_subtable_setup(FT_ULong table_size,
                               FT_ULong classTable,
                               FT_ULong stateArray,
                               FT_ULong entryTable,
                               FT_ULong * classTable_length_p,
                               FT_ULong * stateArray_length_p,
                               FT_ULong * entryTable_length_p,
                               GXV_Validator valid)
{
    FT_ULong o[3];
    FT_ULong* l[3];
    FT_ULong buff[4];


    o[0] = classTable;
    o[1] = stateArray;
    o[2] = entryTable;
    l[0] = classTable_length_p;
    l[1] = stateArray_length_p;
    l[2] = entryTable_length_p;

    gxv_set_length_by_ulong_offset(o, l, buff, 4, table_size, valid);
}


static void
gxv_XClassTable_lookupval_validate(FT_UShort glyph,
                                   GXV_LookupValueCPtr value_p,
                                   GXV_Validator valid)
{
    FT_UNUSED(glyph);

    if (value_p->u >= valid->xstatetable.nClasses)
        FT_INVALID_DATA;
    if (value_p->u > valid->xstatetable.maxClassID)
        valid->xstatetable.maxClassID = value_p->u;
}


/*
 +===============+ --------+
 | lookup header |         |
 +===============+         |
 | BinSrchHeader |         |
 +===============+         |
 | lastGlyph[0]  |         |
 +---------------+         |
 | firstGlyph[0] |         |    head of lookup table
 +---------------+         |             +
 | offset[0]     |    ->   |          offset            [byte]
 +===============+         |             +
 | lastGlyph[1]  |         | (glyphID - firstGlyph) * 2 [byte]
 +---------------+         |
 | firstGlyph[1] |         |
 +---------------+         |
 | offset[1]     |         |
 +===============+         |
 |
   ....                     |
 |
   16bit value array         |
 +===============+         |
 |     value     | <-------+
   ....
 */
static GXV_LookupValueDesc
gxv_XClassTable_lookupfmt4_transit(FT_UShort relative_gindex,
                                   GXV_LookupValueCPtr base_value_p,
                                   FT_Bytes lookuptbl_limit,
                                   GXV_Validator valid)
{
    FT_Bytes p;
    FT_Bytes limit;
    FT_UShort offset;
    GXV_LookupValueDesc value;

    /* XXX: check range? */
    offset = (FT_UShort)(base_value_p->u +
                         relative_gindex * sizeof(FT_UShort));

    p = valid->lookuptbl_head + offset;
    limit = lookuptbl_limit;

    GXV_LIMIT_CHECK(2);
    value.u = FT_NEXT_USHORT(p);

    return value;
}


static void
gxv_XStateArray_validate(FT_Bytes table,
                         FT_ULong* length_p,
                         FT_UShort maxClassID,
                         FT_ULong stateSize,
                         FT_UShort* maxState_p,
                         FT_UShort* maxEntry_p,
                         GXV_Validator valid)
{
    FT_Bytes p = table;
    FT_Bytes limit = table + *length_p;
    FT_UShort clazz;
    FT_UShort entry;

    FT_UNUSED(stateSize);   /* for the non-debugging case */


    GXV_NAME_ENTER("XStateArray");

    GXV_TRACE(("parse % 3d bytes by stateSize=% 3d maxClassID=% 3d\n",
               (int)(*length_p), stateSize, (int)(maxClassID)));

    /*
     * 2 states are predefined and must be described:
     * state 0 (start of text), 1 (start of line)
     */
    GXV_LIMIT_CHECK((1 + maxClassID) * 2 * 2);

    *maxState_p = 0;
    *maxEntry_p = 0;

    /* read if enough to read another state */
    while (p + ((1 + maxClassID) * 2) <= limit)
    {
        (*maxState_p)++;
        for (clazz = 0; clazz <= maxClassID; clazz++)
        {
            entry = FT_NEXT_USHORT(p);
            *maxEntry_p = (FT_UShort)FT_MAX(*maxEntry_p, entry);
        }
    }
    GXV_TRACE(("parsed: maxState=%d, maxEntry=%d\n",
               *maxState_p, *maxEntry_p));

    *length_p = p - table;

    GXV_EXIT;
}


static void
gxv_XEntryTable_validate(FT_Bytes table,
                         FT_ULong* length_p,
                         FT_UShort maxEntry,
                         FT_ULong stateArray_length,
                         FT_UShort maxClassID,
                         FT_Bytes xstatetable_table,
                         FT_Bytes xstatetable_limit,
                         GXV_Validator valid)
{
    FT_Bytes p = table;
    FT_Bytes limit = table + *length_p;
    FT_UShort entry;
    FT_UShort state;
    FT_Int entrySize = 2 + 2 + GXV_GLYPHOFFSET_SIZE(xstatetable);


    GXV_NAME_ENTER("XEntryTable");
    GXV_TRACE(("maxEntry=%d entrySize=%d\n", maxEntry, entrySize));

    if ((p + (maxEntry + 1) * entrySize) > limit)
        FT_INVALID_TOO_SHORT;

    for (entry = 0; entry <= maxEntry; entry++)
    {
        FT_UShort newState_idx;
        FT_UShort flags;
        GXV_XStateTable_GlyphOffsetDesc glyphOffset;


        GXV_LIMIT_CHECK(2 + 2);
        newState_idx = FT_NEXT_USHORT(p);
        flags = FT_NEXT_USHORT(p);

        if (stateArray_length < (FT_ULong)(newState_idx * 2))
        {
            GXV_TRACE(("  newState index 0x%04x points out of stateArray\n",
                       newState_idx));
            if (valid->root->level >= FT_VALIDATE_PARANOID)
                FT_INVALID_OFFSET;
        }

        state = (FT_UShort)(newState_idx / (1 + maxClassID));
        if (0 != (newState_idx % (1 + maxClassID)))
        {
            FT_TRACE4(("-> new state = %d (supposed)\n"
                       "but newState index 0x%04x is not aligned to %d-classes\n",
                       state, newState_idx, 1 + maxClassID));
            if (valid->root->level >= FT_VALIDATE_PARANOID)
                FT_INVALID_OFFSET;
        }

        switch (GXV_GLYPHOFFSET_FMT(xstatetable))
        {
            case GXV_GLYPHOFFSET_NONE:
                glyphOffset.uc = 0; /* make compiler happy */
                break;

            case GXV_GLYPHOFFSET_UCHAR:
                glyphOffset.uc = FT_NEXT_BYTE(p);
                break;

            case GXV_GLYPHOFFSET_CHAR:
                glyphOffset.c = FT_NEXT_CHAR(p);
                break;

            case GXV_GLYPHOFFSET_USHORT:
                glyphOffset.u = FT_NEXT_USHORT(p);
                break;

            case GXV_GLYPHOFFSET_SHORT:
                glyphOffset.s = FT_NEXT_SHORT(p);
                break;

            case GXV_GLYPHOFFSET_ULONG:
                glyphOffset.ul = FT_NEXT_ULONG(p);
                break;

            case GXV_GLYPHOFFSET_LONG:
                glyphOffset.l = FT_NEXT_LONG(p);
                break;

            default:
                if (valid->root->level >= FT_VALIDATE_PARANOID)
                    FT_INVALID_FORMAT;
                goto Exit;
        }

        if (NULL != valid->xstatetable.entry_validate_func)
            valid->xstatetable.entry_validate_func(state,
                                                   flags,
                                                   &glyphOffset,
                                                   xstatetable_table,
                                                   xstatetable_limit,
                                                   valid);
    }

Exit:
    *length_p = p - table;

    GXV_EXIT;
}


FT_LOCAL_DEF(void)
gxv_XStateTable_validate(FT_Bytes table,
                         FT_Bytes limit,
                         GXV_Validator valid)
{
    /* StateHeader members */
    FT_ULong classTable;        /* offset to Class(Sub)Table */
    FT_ULong stateArray;        /* offset to StateArray */
    FT_ULong entryTable;        /* offset to EntryTable */

    FT_ULong classTable_length;
    FT_ULong stateArray_length;
    FT_ULong entryTable_length;
    FT_UShort maxState;
    FT_UShort maxEntry;

    GXV_XStateTable_Subtable_Setup_Func setup_func;

    FT_Bytes p = table;


    GXV_NAME_ENTER("XStateTable");

    GXV_TRACE(("XStateTable header\n"));

    GXV_LIMIT_CHECK(4 + 4 + 4 + 4);
    valid->xstatetable.nClasses = FT_NEXT_ULONG(p);
    classTable = FT_NEXT_ULONG(p);
    stateArray = FT_NEXT_ULONG(p);
    entryTable = FT_NEXT_ULONG(p);

    GXV_TRACE(("nClasses =0x%08x\n", valid->xstatetable.nClasses));
    GXV_TRACE(("offset to classTable=0x%08x\n", classTable));
    GXV_TRACE(("offset to stateArray=0x%08x\n", stateArray));
    GXV_TRACE(("offset to entryTable=0x%08x\n", entryTable));

    if (valid->xstatetable.nClasses > 0xFFFFU)
        FT_INVALID_DATA;

    GXV_TRACE(("StateTable Subtables\n"));

    if (valid->xstatetable.optdata_load_func != NULL)
        valid->xstatetable.optdata_load_func(p, limit, valid);

    if (valid->xstatetable.subtable_setup_func != NULL)
        setup_func = valid->xstatetable.subtable_setup_func;
    else
        setup_func = gxv_XStateTable_subtable_setup;

    setup_func(limit - table,
               classTable,
               stateArray,
               entryTable,
               &classTable_length,
               &stateArray_length,
               &entryTable_length,
               valid);

    if (classTable != 0)
    {
        valid->xstatetable.maxClassID = 0;
        valid->lookupval_sign = GXV_LOOKUPVALUE_UNSIGNED;
        valid->lookupval_func = gxv_XClassTable_lookupval_validate;
        valid->lookupfmt4_trans = gxv_XClassTable_lookupfmt4_transit;
        gxv_LookupTable_validate(table + classTable,
                                 table + classTable + classTable_length,
                                 valid);
        if (valid->subtable_length < classTable_length)
            classTable_length = valid->subtable_length;
    }
    else
    {
        /* XXX: check range? */
        valid->xstatetable.maxClassID =
            (FT_UShort)(valid->xstatetable.nClasses - 1);
    }

    if (stateArray != 0)
        gxv_XStateArray_validate(table + stateArray,
                                 &stateArray_length,
                                 valid->xstatetable.maxClassID,
                                 valid->xstatetable.nClasses,
                                 &maxState,
                                 &maxEntry,
                                 valid);
    else
    {
        maxState = 1; /* 0:start of text, 1:start of line are predefined */
        maxEntry = 0;
    }

    if (maxEntry > 0 && entryTable == 0)
        FT_INVALID_OFFSET;

    if (entryTable != 0)
        gxv_XEntryTable_validate(table + entryTable,
                                 &entryTable_length,
                                 maxEntry,
                                 stateArray_length,
                                 valid->xstatetable.maxClassID,
                                 table,
                                 limit,
                                 valid);

    GXV_EXIT;
}


/*************************************************************************/
/*************************************************************************/
/*****                                                               *****/
/*****                        Table overlapping                      *****/
/*****                                                               *****/
/*************************************************************************/
/*************************************************************************/

static int
gxv_compare_ranges(FT_Bytes table1_start,
                   FT_ULong table1_length,
                   FT_Bytes table2_start,
                   FT_ULong table2_length)
{
    if (table1_start == table2_start)
    {
        if ((table1_length == 0 || table2_length == 0))
            goto Out;
    }
    else if (table1_start < table2_start)
    {
        if ((table1_start + table1_length) <= table2_start)
            goto Out;
    }
    else if (table1_start > table2_start)
    {
        if ((table1_start >= table2_start + table2_length))
            goto Out;
    }
    return 1;

Out:
    return 0;
}


FT_LOCAL_DEF(void)
gxv_odtect_add_range(FT_Bytes start,
                     FT_ULong length,
                     const FT_String * name,
                     GXV_odtect_Range odtect)
{
    odtect->range[odtect->nRanges].start = start;
    odtect->range[odtect->nRanges].length = length;
    odtect->range[odtect->nRanges].name = (FT_String*)name;
    odtect->nRanges++;
}


FT_LOCAL_DEF(void)
gxv_odtect_validate(GXV_odtect_Range odtect,
                    GXV_Validator valid)
{
    FT_UInt i, j;


    GXV_NAME_ENTER("check overlap among multi ranges");

    for (i = 0; i < odtect->nRanges; i++)
        for (j = 0; j < i; j++)
            if (0 != gxv_compare_ranges(odtect->range[i].start,
                                        odtect->range[i].length,
                                        odtect->range[j].start,
                                        odtect->range[j].length))
            {
                if (odtect->range[i].name || odtect->range[j].name)
                    GXV_TRACE(("found overlap between range %d and range %d\n",
                               i, j));
                else
                    GXV_TRACE(("found overlap between `%s' and `%s\'\n",
                               odtect->range[i].name,
                               odtect->range[j].name));
                FT_INVALID_OFFSET;
            }

    GXV_EXIT;
}


/* END */